grammar Language;

options {
 k=6;
}

tokens {
	CONST = 'const_value';
	TRUE = 'true';
	FALSE = 'false';
	NULL = 'null';
	FOR = 'for';
	IN = 'in';
	VAR = 'var';
}

@header {
package ac.jp.u_tokyo.SyncLib.language2;
import java.util.LinkedList;
import java.util.List;
import ac.jp.u_tokyo.SyncLib.language.*;

}

@lexer::header{
package ac.jp.u_tokyo.SyncLib.language2;
import java.io.File;
}

@lexer::members {
    class SaveStruct {
      SaveStruct(CharStream input){
        this.input = input;
        this.marker = input.mark();
      }
      public CharStream input;
      public int marker;
      public File basePath;
     }
     
     File basePath = new File(".");
     public void setBasePath(String path){
     	basePath = new File(path);
     }
     

     Stack<SaveStruct> includes = new Stack<SaveStruct>();

    // We should override this method for handling EOF of included file
     public Token nextToken(){
       Token token = super.nextToken();

       if(token==Token.EOF_TOKEN && !includes.empty()){
        // We've got EOF and have non empty stack.
         SaveStruct ss = includes.pop();
         basePath = ss.basePath;
         setCharStream(ss.input);
         input.rewind(ss.marker);
         token = super.nextToken();
       }

      // Skip first token after switching on another input.
      // You need to use this rather than super as there may be nested include files
       if(((CommonToken)token).getStartIndex() < 0)
         token = this.nextToken();

       return token;
     }
 }

@members {
	HigherProgram _program;
	public static class CustomMsgException extends RecognitionException {
		private String message;
		public String getMessage() {
			return message;
		}
		public CustomMsgException(String msg, int line, int charPositionInLine) {
			message = msg;
    		super.line = line;
    		super.charPositionInLine = charPositionInLine;
    	}
		public CustomMsgException(String msg, IntStream stream) {
			super(stream);
			message = msg;
		}
	}
	
	public void displayRecognitionError(String as[], RecognitionException exp) {
		if (exp instanceof CustomMsgException)
			emitErrorMessage(getErrorHeader(exp) + " " + ((CustomMsgException)exp).getMessage());
		else
			super.displayRecognitionError(as, exp);
	}
	

}


fragment DIGIT 
	:	'0'..'9';
	
fragment ULETTER
	:	'A'..'Z';
fragment LLETTER
	:	'a'..'z';
fragment LETTER
	:	ULETTER | LLETTER | '_';	

NUMBER : DIGIT+;
NAME 	:	LETTER (DIGIT | LETTER)*;
STRING : ('\"' | '\'') (~('\"' | '\''))* ('\"' | '\'');


COMMENT			
		:	'/*'  ( options {greedy=false;}:.)* '*/' 			  {skip();}
		|	'/' '\\' '\n' '*'  ( options {greedy=false;}:.)* '*/' {skip();}
		;
LINE_COMMENT :	'//'(~('\n' | '\r')*)('\n' | '\r') {skip();};

WS
    : (' '| '\t' | '\n' | '\r')+ {skip();};
    
 INCLUDE
     : 'include' (WS)? f=STRING {
       String name = f.getText();
       name = name.substring(1,name.length()-1);
       try {
        // save current lexer's state
         SaveStruct ss = new SaveStruct(input);
         ss.basePath = basePath;
         includes.push(ss);
         
         name = basePath.getAbsolutePath() + File.separator + name;
         basePath = new File(name).getAbsoluteFile().getParentFile();

        // switch on new input stream
         setCharStream(new ANTLRFileStream(name));
         reset();

       } catch(Exception fnf) { throw new Error("Cannot open file " + name); }
     }
     ;
    
  
      

prog returns [HigherProgram result]
 	: {_program = new HigherProgram();}synchronizer_decls EOF {$result = _program;};
synchronizer_decls 
	:	s1=synchronizer_decl 
	{_program.addDecl(s1);} 
	(synchronizer_decls)?;
	
synchronizer_decl returns [HigherSyncDecl result]
	: {$result = new HigherSyncDecl();}	
	NAME
	{$result.setName($NAME.text);}
	(generic_para_list[$result])? para_list[$result] ':=' 
	disjunction {$result.setSync($disjunction.result);};
	
generic_para_list[HigherSyncDecl decl]
	: '<' generic_para_decl[decl] (',' generic_para_decl[decl])* '>';
	
generic_para_decl[HigherSyncDecl decl]
	: {Const defaultValue = null;} 
	NAME ('=' const_value {defaultValue = $const_value.result;})?
	{ decl.addGenericParameter($NAME.text, defaultValue, $NAME.line);} 
	;
	
para_list[HigherSyncDecl decl]
	: '(' para[decl] (',' para[decl])* ')';
	
para[HigherSyncDecl decl]
 	:	var {decl.addVar($var.result);};

disjunction returns [SyncConstruct result] 
	: s1=single_sync 
	{ Disjunction disjunction = new Disjunction(s1.getLine());
	  disjunction.addInner(s1);}
	('|' s2=single_sync {disjunction.addInner(s2);})*
	{if (disjunction.getInnerCount() == 1) 
		$result = disjunction.getFirst();
	 else
		$result = disjunction;
	}
	;
	
single_sync returns [SyncConstruct result]
	:	 sync_ref {$result = $sync_ref.result;}
	| conjunction {$result = $conjunction.result;} 
	| map {$result = $map.result;}
	| expr {$result = $expr.result;}
	| '(' disjunction ')' {$result = $disjunction.result;};
	
conjunction returns [Conjunction result]
	:	start='{' { $result = new Conjunction($start.line);} 
	var_decl[$result]? 
	inner1=disjunction ';' { $result.addInner($inner1.result);} 
	(inner2=disjunction ';' { $result.addInner($inner2.result);})*
	'}';
	 
var_decl[Conjunction c]
	: VAR v1=var{c.addVar(v1);} (',' v2=var {c.addVar(v2);})* ';';

sync_ref returns [SyncRefConstruct result]
	: NAME
	{ $result = new SyncRefConstruct($NAME.line, _program.getLowerProgram());
	  $result.setName($NAME.text);
	}
	 generic_arg_list[$result]? var_list[$result];

generic_arg_list[SyncRefConstruct c]
	:	'<' NAME '=' const_value '>' {c.addGenericArg($NAME.text, $const_value.result);};
	
var_list[SyncRefConstruct c]
	:	'(' v1=var {c.addArg(v1);} 
	(',' v2=var {c.addArg(v2);})* ')';

expr returns [SyncConstruct result]
	:  {Part left = null; VarExprPart leftVarExpr = null; VarExpr rightExpr = null; String rightRef = null; Const rightConst = null;}
	( e1=varexpr {leftVarExpr = new VarExprPart(e1, true);  left = leftVarExpr;} 
	  | v1=var {left = new VarPart(v1);}
	) 
	op { if ($op.result != ExprType.EQUAL && leftVarExpr != null) leftVarExpr.setAtLeft(false);}
	( e2=varexpr { rightExpr = e2;} 
	  | NAME {rightRef = $NAME.text;}  
	  | const_value_no_ref {rightConst = $const_value_no_ref.result;} 
	)
	{ $result = Helper.constructExpr($op.line, left, rightExpr, rightRef, rightConst, $op.result);}
	;

varexpr returns [VarExpr result]
	:	v1=var '.' c1=const_value 
	{SGetExpr s = new SGetExpr(v1.getLine()); s.setKey(c1); s.setDict(v1); $result=s;} 
	| v2=var '.' '!' v3=var  {$result = new KVDGetExpr(v2.getLine(), v2, v3);}
	| '!' v4=var '.' v5=var {$result = new DVDGetExpr(v4.getLine(), v4, v5);}
	| '!' v6=var '.' '!' v7=var {$result = new KDVDGetExpr(v6.getLine(), v6, v7);}; 

op returns [ExprType result, int line]
	:	c1='=' {$result = ExprType.EQUAL; $line=$c1.line;} 
	| c2='==' {$result = ExprType.TESTEQUAL; $line=$c2.line;}
	| c3='<>' {$result = ExprType.NOTEQUAL;$line=$c3.line;};

map returns [ForMap result]	
	:	FOR {$result = new ForMap($FOR.line);} 
	map_var_list[$result]  
	IN 
	dict_list[$result] 
	single_sync{$result.setSyncConstruct($single_sync.result);};

map_var_list[ForMap c]
	:	'[' entry_bind[c] (',' entry_bind[c])* ']';
	
entry_bind[ForMap c]
	:	v1=var {c.addValueBinding(v1);} 
	| '<' k2=var ',' v2=var '>' {c.addPairBinding(k2, v2);};
	
dict_list[ForMap c]
	:	('[' {c.setEqualMap(true);} | '<'{c.setEqualMap(false);}) 
	v1=var {c.addDictionary(v1);} 
	(',' v2=var {c.addDictionary(v2);})* 
	('>' | ']');
	

var	returns [Var result]
	:	NAME {$result = new Var($NAME.text, $NAME.line);};

const_value returns [Const result]
	: value_ref {$result = $value_ref.result;}
	| const_value_no_ref {$result = $const_value_no_ref.result;};
	
const_value_no_ref returns [Const result]
	: boolean_value {$result = $boolean_value.result;} 
	| string {$result = new StringConst($string.line, $string.text);}
	| NUMBER {$result = new IntegerConst($NUMBER.line, new Integer($NUMBER.text));}
	| NULL {$result = new NullConst($NULL.line);}
	| '[' value_list  ']' {$result = $value_list.result;};

value_list returns [ArrayConst result]
	: 
	(p1=const_value {$result = new ArrayConst(p1.getLine());$result.addInner($p1.result);} 
	(',' p2=const_value {$result.addInner($p2.result);})*)?;

boolean_value returns [BooleanConst result]
	: TRUE {$result = new BooleanConst($TRUE.line, true);}
	| FALSE {$result = new BooleanConst($FALSE.line, false);};

value_ref returns [ConstRef result]
	: NAME {$result = new ConstRef($NAME.line, $NAME.text);};

string returns [String text, int line]
	: STRING {$text = $STRING.text.substring(1, $STRING.text.length() - 1); $line = $STRING.line;};